﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using CodeContrib.Web.Routing;

namespace CodeContrib.Web.Mvc.Html
{
	public static class PagingExtensions
	{
		private static string _firstPageText;
		private static string _lastPageText;
		private static string _nextPageText;
		private static string _pageNumberKey;
		private static string _pageSizeKey;
		private static IEnumerable<int> _pageSizes;
		private static string _previousPageText;
		private static int _visiblePageCount;

		public static string FirstPageText
		{
			get
			{
				if (string.IsNullOrEmpty(_firstPageText))
				{
					_firstPageText = "&lt;&lt;";
				}

				return _firstPageText;
			}
			set { _firstPageText = value; }
		}

		public static string LastPageText
		{
			get
			{
				if (string.IsNullOrEmpty(_lastPageText))
				{
					_lastPageText = "&gt;&gt;";
				}

				return _lastPageText;
			}
			set { _lastPageText = value; }
		}

		public static string NextPageText
		{
			get
			{
				if (string.IsNullOrEmpty(_nextPageText))
				{
					_nextPageText = "&gt;";
				}

				return _nextPageText;
			}
			set { _nextPageText = value; }
		}

		public static string PageNumberKey
		{
			get
			{
				if (string.IsNullOrEmpty(_pageNumberKey))
				{
					_pageNumberKey = "page";
				}

				return _pageNumberKey;
			}
			set { _pageNumberKey = value; }
		}

		public static string PageSizeKey
		{
			get
			{
				if (string.IsNullOrEmpty(_pageSizeKey))
				{
					_pageSizeKey = "size";
				}

				return _pageSizeKey;
			}
			set { _pageSizeKey = value; }
		}

		public static IEnumerable<int> PageSizes
		{
			get
			{
				if (_pageSizes == null
				    || !_pageSizes.Any())
				{
					_pageSizes = new[]
						             {
							             10, 25, 50, 100
						             };
				}

				return _pageSizes;
			}
			set { _pageSizes = value; }
		}

		public static string PreviousPageText
		{
			get
			{
				if (string.IsNullOrEmpty(_previousPageText))
				{
					_previousPageText = "&lt;";
				}

				return _previousPageText;
			}
			set { _previousPageText = value; }
		}

		public static int VisiblePageCount
		{
			get { return _visiblePageCount; }
			set
			{
				if (value > 0)
				{
					_visiblePageCount = value;
				}
			}
		}

		static PagingExtensions()
		{
			VisiblePageCount = 10;
		}

		private static TagBuilder GetActivePage(HtmlHelper htmlHelper, string content, int pageNumber, int pageSize)
		{
			var link = new TagBuilder("a")
				           {
					           InnerHtml = content
				           };

			link.Attributes["href"] = GetPageUrl(htmlHelper, pageNumber, pageSize);

			return GetPage(link.ToString());
		}

		private static TagBuilder GetCurrentPage(string content)
		{
			var link = new TagBuilder("a")
				           {
					           InnerHtml = content
				           };

			link.Attributes["href"] = "#";

			var page = GetPage(link.ToString());
			page.AddCssClass("active");

			return page;
		}

		private static TagBuilder GetInactivePage(string content)
		{
			var link = new TagBuilder("a")
				           {
					           InnerHtml = content
				           };

			link.Attributes["href"] = "#";

			var page = GetPage(link.ToString());
			page.AddCssClass("disabled");

			return page;
		}

		private static TagBuilder GetPage(string content)
		{
			return new TagBuilder("li")
				       {
					       InnerHtml = content
				       };
		}

		private static IEnumerable<int> GetPageNumbers(int pageCount)
		{
			for (var i = 1; i <= pageCount; i++)
			{
				yield return i;
			}
		}

		private static string GetPageUrl(HtmlHelper htmlHelper, int pageNumber, int pageSize)
		{
			var routeValues = new RouteValueDictionary();
			routeValues.Merge(htmlHelper.ViewContext.RequestContext.RouteData.Values, true);
			routeValues.Merge(htmlHelper.ViewContext.RequestContext.HttpContext.Request.QueryString.ToRouteValueDictionary(), true);
			routeValues.Merge(PageNumberKey, pageNumber, true);
			routeValues.Merge(PageSizeKey, pageSize, true);

			var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);

			// For some reason, the fragment is always empty!?
			var fragment = htmlHelper.ViewContext.HttpContext.Request.Url.Fragment;

			return urlHelper.Action(null /* actionName */, null /* controllerName */, routeValues, null /* protocol */, null /* hostName */, fragment);
		}

		public static MvcHtmlString PageNumber(this HtmlHelper htmlHelper, IPaging paging)
		{
			return PageNumber(htmlHelper, paging.PageNumber, paging.PageSize, paging.TotalCount, null);
		}

		public static MvcHtmlString PageNumber(this HtmlHelper htmlHelper, IPaging paging, object htmlAttributes)
		{
			return PageNumber(htmlHelper, paging.PageNumber, paging.PageSize, paging.TotalCount, htmlAttributes);
		}

		public static MvcHtmlString PageNumber(this HtmlHelper htmlHelper, int pageNumber, int pageSize, int totalCount)
		{
			return PageNumber(htmlHelper, pageNumber, pageSize, totalCount, null);
		}

		public static MvcHtmlString PageNumber(this HtmlHelper htmlHelper, int pageNumber, int pageSize, int totalCount, object htmlAttributes)
		{
			if (pageNumber < 1
			    && !int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageNumberKey)), out pageNumber))
			{
				pageNumber = 1;
			}

			if (pageSize < 1
			    && !int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageSizeKey)), out pageSize))
			{
				throw new ArgumentOutOfRangeException("pageSize");
			}

			var pageCount = 1;

			if (totalCount > 0)
			{
				pageCount = (int)Math.Ceiling(totalCount / (double)pageSize);

				if (pageNumber > pageCount)
				{
					pageNumber = pageCount;
				}
			}

			var hidden = htmlHelper.Hidden(PageSizeKey, pageSize);
			var label = htmlHelper.Label(PageSizeKey, "Page:");

			var pageNumberHtmlAttributes = new
				                               {
					                               @class = "input-small change-submit"
				                               }.ToDictionary();

			if (pageCount == 1)
			{
				pageNumberHtmlAttributes.Merge("disabled", "disabled", true);
			}

			var pageNumbers = htmlHelper.DropDownList(PageNumberKey, GetPageNumbers(pageCount).ToSelectList(pageNumber), pageNumberHtmlAttributes);
			var submitButton = htmlHelper.SubmitButton("Go", new
				                                                 {
					                                                 @class = "btn"
				                                                 });

			var form = new TagBuilder("form")
				           {
					           InnerHtml = label.ToHtmlString() + pageNumbers.ToHtmlString() + hidden.ToHtmlString() + submitButton
				           };
			form.AddCssClass("form-inline center");
			form.Attributes["method"] = HtmlHelper.GetFormMethodString(FormMethod.Get);

			var navigation = new TagBuilder("nav")
				                 {
					                 InnerHtml = form.ToString()
				                 };
			navigation.MergeAttributes(htmlAttributes.ToHtmlAttributes());
			navigation.AddCssClass("block");

			return MvcHtmlString.Create(navigation.ToString());
		}

		public static MvcHtmlString PageSize(this HtmlHelper htmlHelper)
		{
			return PageSize(htmlHelper, null);
		}

		public static MvcHtmlString PageSize(this HtmlHelper htmlHelper, object htmlAttributes)
		{
			int currentPageSize;

			if (!int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageSizeKey)), out currentPageSize))
			{
				currentPageSize = PageSizes.First();
			}

			var hidden = htmlHelper.Hidden(PageNumberKey, 1);
			var label = htmlHelper.Label(PageSizeKey, "Showing:");

			var pageSizesHtmlAttributes = new
				                              {
					                              @class = "input-small change-submit"
				                              }.ToDictionary();

			if (PageSizes.Count() == 1)
			{
				pageSizesHtmlAttributes.Merge("disabled", "disabled", true);
			}

			var pageSizes = htmlHelper.DropDownList(PageSizeKey, PageSizes.ToSelectList(currentPageSize), pageSizesHtmlAttributes);
			var submitButton = htmlHelper.SubmitButton("Go", new
				                                                 {
					                                                 @class = "btn"
				                                                 });

			var form = new TagBuilder("form")
				           {
					           InnerHtml = hidden.ToHtmlString() + label.ToHtmlString() + pageSizes.ToHtmlString() + submitButton
				           };
			form.AddCssClass("form-inline center");
			form.Attributes["method"] = HtmlHelper.GetFormMethodString(FormMethod.Get);

			var navigation = new TagBuilder("nav")
				                 {
					                 InnerHtml = form.ToString()
				                 };
			navigation.MergeAttributes(htmlAttributes.ToHtmlAttributes());
			navigation.AddCssClass("block");

			return MvcHtmlString.Create(navigation.ToString());
		}

		public static MvcHtmlString Pager(this HtmlHelper htmlHelper, IPaging paging)
		{
			return Pager(htmlHelper, paging.PageNumber, paging.PageSize, paging.TotalCount, null);
		}

		public static MvcHtmlString Pager(this HtmlHelper htmlHelper, IPaging paging, object htmlAttributes)
		{
			return Pager(htmlHelper, paging.PageNumber, paging.PageSize, paging.TotalCount, htmlAttributes);
		}

		public static MvcHtmlString Pager(this HtmlHelper htmlHelper, int pageNumber, int pageSize, int totalCount)
		{
			return Pager(htmlHelper, pageNumber, pageSize, totalCount, null);
		}

		public static MvcHtmlString Pager(this HtmlHelper htmlHelper, int pageNumber, int pageSize, int totalCount, object htmlAttributes)
		{
			if (pageNumber < 1
			    && !int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageNumberKey)), out pageNumber))
			{
				pageNumber = 1;
			}

			if (pageSize < 1
			    && !int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageSizeKey)), out pageSize))
			{
				throw new ArgumentOutOfRangeException("pageSize");
			}

			var pageCount = 1;

			if (totalCount > 0)
			{
				pageCount = (int)Math.Ceiling(totalCount / (double)pageSize);

				if (pageNumber > pageCount)
				{
					pageNumber = pageCount;
				}
			}

			var innerHtml = new StringBuilder();

			TagBuilder previousPage;

			if (pageNumber > 1)
			{
				previousPage = GetActivePage(htmlHelper, "Previous", pageNumber + 1, pageSize);
			}
			else
			{
				previousPage = GetInactivePage("Previous");
			}

			previousPage.AddCssClass("previous");

			innerHtml.Append(previousPage);

			TagBuilder nextPage;

			if (pageNumber < pageCount)
			{
				nextPage = GetActivePage(htmlHelper, "Next", pageNumber + 1, pageSize);
			}
			else
			{
				nextPage = GetInactivePage("Next");
			}

			nextPage.AddCssClass("next");

			innerHtml.Append(nextPage);

			var pager = new TagBuilder("ul")
				            {
					            InnerHtml = innerHtml.ToString()
				            };
			pager.AddCssClass("pager");

			var navigation = new TagBuilder("nav")
				                 {
					                 InnerHtml = pager.ToString()
				                 };
			navigation.MergeAttributes(htmlAttributes.ToHtmlAttributes());

			return MvcHtmlString.Create(navigation.ToString());
		}

		public static MvcHtmlString Pagination(this HtmlHelper htmlHelper, IPaging paging)
		{
			return Pagination(htmlHelper, paging.PageNumber, paging.PageSize, paging.TotalCount, null);
		}

		public static MvcHtmlString Pagination(this HtmlHelper htmlHelper, IPaging paging, object htmlAttributes)
		{
			return Pagination(htmlHelper, paging.PageNumber, paging.PageSize, paging.TotalCount, htmlAttributes);
		}

		public static MvcHtmlString Pagination(this HtmlHelper htmlHelper, int pageNumber, int pageSize, int totalCount)
		{
			return Pagination(htmlHelper, pageNumber, pageSize, totalCount, null);
		}

		public static MvcHtmlString Pagination(this HtmlHelper htmlHelper, int pageNumber, int pageSize, int totalCount, object htmlAttributes)
		{
			if (pageNumber < 1
			    && !int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageNumberKey)), out pageNumber))
			{
				pageNumber = 1;
			}

			if (pageSize < 1
			    && !int.TryParse(Convert.ToString(htmlHelper.ViewContext.Eval(PageSizeKey)), out pageSize))
			{
				throw new ArgumentOutOfRangeException("pageSize");
			}

			var pageCount = 1;

			if (totalCount > 0)
			{
				pageCount = (int)Math.Ceiling(totalCount / (double)pageSize);

				if (pageNumber > pageCount)
				{
					pageNumber = pageCount;
				}
			}

			var innerHtml = new StringBuilder();

			TagBuilder firstPage = null;
			TagBuilder previousPage = null;

			// First and previous.
			if (pageNumber == 1)
			{
				if (!string.IsNullOrEmpty(FirstPageText))
				{
					firstPage = GetInactivePage(FirstPageText);
				}

				if (!string.IsNullOrEmpty(PreviousPageText))
				{
					previousPage = GetInactivePage(PreviousPageText);
				}
			}
			else
			{
				if (!string.IsNullOrEmpty(FirstPageText))
				{
					firstPage = GetActivePage(htmlHelper, FirstPageText, 1, pageSize);
				}

				if (!string.IsNullOrEmpty(PreviousPageText))
				{
					previousPage = GetActivePage(htmlHelper, PreviousPageText, pageNumber - 1, pageSize);
				}
			}

			if (firstPage != null)
			{
				innerHtml.Append(firstPage);
			}

			if (previousPage != null)
			{
				innerHtml.Append(previousPage);
			}

			var firstPageNumber = 1;
			var lastPageNumber = pageCount;

			if (pageCount > VisiblePageCount)
			{
				var pageNumberRange = (int)Math.Ceiling(VisiblePageCount / 2d) - 1;
				var minPageNumber = (pageNumber - pageNumberRange);
				var maxPageNumber = (pageNumber + pageNumberRange);

				if (minPageNumber < 2)
				{
					maxPageNumber = VisiblePageCount;
					minPageNumber = 1;
				}
				else if (maxPageNumber > (pageCount - 1))
				{
					maxPageNumber = pageCount;
					minPageNumber = (pageCount - VisiblePageCount) + 1;
				}

				firstPageNumber = minPageNumber;
				lastPageNumber = maxPageNumber;
			}

			for (var i = firstPageNumber; i <= lastPageNumber; i++)
			{
				if (i == pageNumber)
				{
					innerHtml.Append(GetCurrentPage(i.ToString()));
				}
				else
				{
					var activePage = GetActivePage(htmlHelper, i.ToString(), i, pageSize);
					activePage.AddCssClass("page");

					innerHtml.Append(activePage);
				}
			}

			TagBuilder nextPage = null;
			TagBuilder lastPage = null;

			// Next and last.
			if (pageNumber == pageCount)
			{
				if (!string.IsNullOrEmpty(NextPageText))
				{
					nextPage = GetInactivePage(NextPageText);
				}

				if (!string.IsNullOrEmpty(LastPageText))
				{
					lastPage = GetInactivePage(LastPageText);
				}
			}
			else
			{
				if (!string.IsNullOrEmpty(NextPageText))
				{
					nextPage = GetActivePage(htmlHelper, NextPageText, pageNumber + 1, pageSize);
				}

				if (!string.IsNullOrEmpty(LastPageText))
				{
					lastPage = GetActivePage(htmlHelper, LastPageText, pageCount, pageSize);
				}
			}

			if (nextPage != null)
			{
				innerHtml.Append(nextPage);
			}

			if (lastPage != null)
			{
				innerHtml.Append(lastPage);
			}

			var pagination = new TagBuilder("ul")
				                 {
					                 InnerHtml = innerHtml.ToString()
				                 };

			var navigation = new TagBuilder("nav")
				                 {
					                 InnerHtml = pagination.ToString()
				                 };
			navigation.MergeAttributes(htmlAttributes.ToHtmlAttributes());
			navigation.AddCssClass("pagination pagination-centered");

			return MvcHtmlString.Create(navigation.ToString());
		}
	}
}